/* ----------------------------------------------------------------------------
* File: user_box.cpp
*
* Desc: Definition of CUserBox class
*
* Created: 19/02/2011, by Alexey Korshunov
* ---------------------------------------------------------------------------- */
#include "user_box.h"
#include "utils.h"


void on_exit_thread(int sig)
{
	return;
}

// Thread function for detecting changes in the file map
// param - pointer to a box_environment structure
void* thread_file_map(void *param)
{
	bool changes;
	struct timespec ts;

	// Disable cancellation of current thread
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

	box_environment *env = static_cast<box_environment*>(param);
	if (env == NULL)
		pthread_exit(NULL);

	// Setup signal handler
	signal(SIGUSR1, &on_exit_thread);

	pthread_mutex_lock(&env->mutex_start);
	pthread_cond_broadcast(&env->cv_start);
	pthread_mutex_unlock(&env->mutex_start);
	
	try
	{
		// Create logger
		CFileLogger logger("./thread_file_map.log");
		logger.Print("thread file map: Start\n");

		while(!env->stop)
		{
			changes = false;

			// Wait signal about change file
			clock_gettime(CLOCK_REALTIME, &ts);
			ts.tv_sec += BOX_WAIT_FILE_CHANGE;
			pthread_mutex_lock(&env->mutex_change);
			if (pthread_cond_timedwait(&env->cv_change, &env->mutex_change, &ts) != ETIMEDOUT)
				changes = true;
			pthread_mutex_unlock(&env->mutex_change);

			if (changes)
			{
				logger.Print("thread file map: Detected changes\n");

				// Reread file map
				if (env->box->ReadMap() == BOX_SERVER_OK)
				{
					// Get vector of changed files
					std::vector<file_info*> *vector = env->box->GetChangedFiles();

					// Notify subscriber
					env->box->NotifySubscribers(vector);
					logger.Print("thread file map: Subscribers notified\n");
				}
			}
			sleep(1);
		}
	}
	catch(...)
	{
		pthread_exit(NULL);
	}

	pthread_exit(NULL);
}

/************************************************************************/
/*------------------ CUserBox::CUserFile section -----------------------*/
/************************************************************************/

CUserBox::CUserFile::CUserFile(const char* file, const char* mode) throw (std::exception):
m_file_name(file),
m_mode(mode),
m_fp(NULL),
m_eof(false),
m_size(0)
{
	m_fp = fopen(m_file_name, m_mode);
	if (m_fp == NULL)
		throw std::exception();

	struct stat64 stat_data;
	if (stat64(m_file_name, &stat_data) == -1)
		throw std::exception();
	
	m_size = stat_data.st_size;
}


CUserBox::CUserFile::~CUserFile()
{
	fclose(m_fp);
}

/************************************************************************/
/*	Desc:
		Reset state of file, seeking at the start												
	Params:
		none
	Return:
		0 if success, -1 if error										*/
/************************************************************************/
int CUserBox::CUserFile::Reset()
{
	int ret = fseek(m_fp, 0, SEEK_SET);
	if (ret != 0)
		return BOX_SERVER_ERROR;

	m_eof = false;
	return BOX_SERVER_OK;
}


/************************************************************************/
/*	Desc:
		Read data to the input buffer												
	Params:
		data - input buffer 
		size - size data in bytes
	Return:
		actually read bytes if success, -1 if error						*/
/************************************************************************/
size_t CUserBox::CUserFile::ReadData(unsigned char* data, size_t size)
{
	size_t bytes = 0;

	if (m_eof == true)
		return BOX_SERVER_ERROR;

	bytes = fread(data, 1, size, m_fp);
	if (bytes < size)
	{
		if (feof(m_fp))
			m_eof = true;
		if (ferror(m_fp))
			return BOX_SERVER_ERROR;
	}

	return bytes;
}


/************************************************************************/
/*	Desc:
		Write input data to the file												
	Params:
		data - input buffer 
		size - size data in bytes
	Return:	
		actually written bytes if success, -1 if error					*/
/************************************************************************/
size_t CUserBox::CUserFile::WriteData(unsigned char* data, size_t size)
{
	size_t bytes = 0;

	bytes = fwrite(data, 1, size, m_fp);
	if (ferror(m_fp))
		return BOX_SERVER_ERROR;

	return bytes;
}






/************************************************************************/
/*------------------------ CUserBox section ----------------------------*/
/************************************************************************/

CUserBox::CUserBox(CBoxServer* parent):
m_id(0),
m_thread_change((pthread_t)0),
m_user_profile(NULL)
{
	pthread_mutex_init(&m_mutex_subscriber, NULL);
}


CUserBox::~CUserBox()
{
	// Destroy all subscribers
	typename VectorSubscribers::iterator it = m_subscribers.begin();
	for (; it != m_subscribers.end(); ++it)
		delete (*it);
	m_subscribers.clear();

	pthread_mutex_destroy(&m_mutex_subscriber);

	// Delete logger
	delete m_env.logger;
}

// Open box
int CUserBox::Open(const char *username, unsigned int id)
{
	char log_name[256];
	struct timespec ts;
	int ret;

	if (m_env.opened)
		return BOX_SERVER_OK;

	// Create logger
	sprintf(log_name, "./user_box_%d.log", id);
	m_env.logger = new CFileLogger();
	if ((ret = m_env.logger->Open(log_name)) != BOX_SERVER_OK)
		return ret;

	m_env.logger->Print("user box: Opening...\n");
	m_id = id;

	// Read user profile
	m_env.logger->Print("user box: Read user profile...");
	m_user_profile = (IUserProfile*)new CTestUserProfile(username, m_env.logger);
	if ((ret = m_user_profile->ReadProfile()) != BOX_SERVER_OK)
	{
		m_env.logger->Print("\nERROR: Unable to read user profile\n");
		delete m_user_profile;
		return ret;
	}
	m_env.logger->Print("success\n");


	// Add files to file map for current user profile
	m_env.logger->Print("user_box: Process file data from user profile...\n");
	user_profile *info = (user_profile*)m_user_profile->GetProfileInfo();
	if (info != NULL)
	{
		std::map<unsigned int, user_box_info*>::iterator it_box = info->boxes.begin();
		user_box_info *binfo = it_box->second;
		
		std::map<unsigned int, file_info*>::iterator it = binfo->files.begin();
		for (; it != binfo->files.end(); ++it)
		{
			file_info *finfo = it->second;

			// check accessibility
			m_env.logger->Print("user box: Access of file %s", finfo->name);
			if (access(finfo->name, R_OK) == -1)
			{
				m_env.logger->Print("\nERROR: Unable to access file %s\n", finfo->name);
				continue;
			}
			m_env.logger->Print("user box: success\n");

			// add to file map
			m_env.logger->Print("user box: Add file to map...");
			{
				CMutexLocker lock(m_env.mutex_map);
				if (m_env.map_files.find(finfo->name) != m_env.map_files.end())
				{
					m_env.logger->Print("\nFile is already in map\n");
					continue;
				}
				m_env.map_files[finfo->name] = finfo;
			}
			m_env.logger->Print("success\n");
			
		}
	}

	// Create notification thread
	m_env.logger->Print("user box: Create notification thread...");
	m_env.stop = false;
	if (UtilsThreadCreate(&m_thread_change, 0, &thread_file_map, (void*)&m_env, m_env.logger->GetFileDescriptor()) != UTILS_OK)
		return BOX_SERVER_ERROR;

	// Wait worker thread
	pthread_mutex_lock(&m_env.mutex_start);
	while(1)
	{
		clock_gettime(CLOCK_REALTIME, &ts);
		ts.tv_sec += BOX_START_THREAD_TIMEOUT;
		if (pthread_cond_timedwait(&m_env.cv_start, &m_env.mutex_start, &ts) != ETIMEDOUT)
			break;

		if (UtilsThreadIsExist(&m_thread_change) != UTILS_OK)	// thread doesn't exist already
			break;
	}
	pthread_mutex_unlock(&m_env.mutex_start);

	if (UtilsThreadIsExist(&m_thread_change) != UTILS_OK)
	{
		m_env.logger->Print("ERROR: Worker thread is failed\n");
		return BOX_SERVER_ERROR;
	}

	m_env.logger->Print("success\n\n");

	m_env.opened = true;	
	return BOX_SERVER_OK;
}

// Close box
int CUserBox::Close()
{
	struct timespec ts;
	int status;

	if (!m_env.opened)
	{
		m_env.logger->Print("user box: Box already is closed\n");
		return BOX_SERVER_OK;
	}

	m_env.logger->Print("user box: Closing...\n");


	// Cancel thread
	m_env.logger->Print("user box: Cancel notification thread...");
	m_env.stop = true;
	if (UtilsThreadKill(&m_thread_change, SIGUSR1, &status) != UTILS_OK)
	{
		m_env.logger->Print("\nuser box: Unable to kill thread, %s", strerror(status));
		return BOX_SERVER_ERROR;
	}
	m_env.logger->Print("success\n");


	// Wait thread
	m_env.logger->Print("user box: Wait thread...");
	pthread_mutex_lock(&m_env.mutex_stop);
	while(1)
	{
		clock_gettime(CLOCK_REALTIME, &ts);
		ts.tv_sec += BOX_STOP_THREAD_TIMEOUT;
		if (pthread_cond_timedwait(&m_env.cv_stop, &m_env.mutex_stop, &ts) != ETIMEDOUT)
			break;

		if (UtilsThreadIsExist(&m_thread_change) != UTILS_OK)	// thread already doesn't exist
			break;
	}
	pthread_mutex_unlock(&m_env.mutex_stop);
	m_env.logger->Print("success\n");


	m_env.opened = false;
	m_env.logger->Print("user box: Closed\n");
	return BOX_SERVER_OK;
}


// Add file into the box
int CUserBox::AddFile(file_info* finfo)
{
	if (finfo == NULL)
		return BOX_SERVER_ERROR;
	
	{
		CMutexLocker lock(m_env.mutex_map);
		if (m_env.map_files.find(finfo->name) != m_env.map_files.end())
			return BOX_SERVER_ERROR;	// file is already in map
		m_env.map_files[finfo->name] = finfo;
	}
	
	pthread_cond_signal(&m_env.cv_change);	// signal about changes
	return BOX_SERVER_OK;
}

// Delete file from box
int CUserBox::DeleteFileByName(const char* name)
{
	if (name == NULL)
		return BOX_SERVER_ERROR;

	{
		CMutexLocker lock(m_env.mutex_map);
		typename MapFiles::iterator it = m_env.map_files.find(name);
		if (it == m_env.map_files.end())
			return BOX_SERVER_ERROR;

		delete it->second;
		m_env.map_files.erase(it);
	}
	
	pthread_cond_signal(&m_env.cv_change);	// signal about changes
	return BOX_SERVER_OK;
}

// Get count of files
size_t CUserBox::CountFiles()
{ 
	CMutexLocker lock(m_env.mutex_map);
	return m_env.map_files.size();
}

// Get file info by identifier
const file_info* CUserBox::GetFileInfoById(unsigned int id)
{ 
	CMutexLocker lock(m_env.mutex_map);
	typename MapFiles::iterator it = m_env.map_files.begin();
	for (; it != m_env.map_files.end(); ++it)
	{
		if (it->second->id == id)
			return it->second;
	}
	return NULL;
}

// Get file info by name
const file_info* CUserBox::GetFileInfoByName(const char* name)
{ 
	CMutexLocker lock(m_env.mutex_map);
	typename MapFiles::iterator it = m_env.map_files.find(name);
	if (it != m_env.map_files.end())
		return it->second;

	return NULL;
}

// Read file map
int CUserBox::ReadMap()
{
	CMutexLocker lock(m_env.mutex_map);

	typename MapFiles::iterator it = m_env.map_files.begin();
	for (; it != m_env.map_files.end(); ++it)
	{
		file_info* info = it->second;

		// check accessibility
		if (access(info->name, R_OK) == -1)
		{
			info->is_access = false;
			continue;
		}

		// get statistics
		struct stat64 stat_data;
		if (stat64(info->name, &stat_data) == -1)
			continue;

		info->atime = stat_data.st_atime;
		info->ctime = stat_data.st_ctime;
		info->mtime = stat_data.st_mtime;
		info->size = stat_data.st_size;
		if (stat_data.st_mode & S_IFDIR)
			info->is_directory = true;
	}

	return BOX_SERVER_OK;
}

// Get vector of changed files
std::vector<file_info*>* CUserBox::GetChangedFiles()
{
	std::vector<file_info*> vector;
	return &vector;
}


// Add subscriber of this box
void CUserBox::AddSubscriber(CBoxConnection* obj)
{
	CMutexLocker lock(m_mutex_subscriber);
	m_subscribers.push_back(obj);
}

// Delete subscriber
void CUserBox::DeleteSubscriber(CBoxConnection* obj)
{
	CMutexLocker lock(m_mutex_subscriber);
	typename VectorSubscribers::iterator it = find(m_subscribers.begin(), m_subscribers.end(), obj);
	if (it != m_subscribers.end())
		m_subscribers.erase(it);
}

// Get count subscribers
size_t CUserBox::CountSubscribers()
{
	CMutexLocker lock(m_mutex_subscriber);
	return m_subscribers.size();
}

// Notify subscribers about changes
void CUserBox::NotifySubscribers(std::vector<file_info*> *finfo_vector)
{
	//char buf[] = "FILE MAP CHANGES";

	CMutexLocker lock(m_mutex_subscriber);

	//VectorConnectionsIterator it = m_subscribers.begin();
	//for (; it != m_subscribers.end(); ++it)
	//	(*it)->SendControlMsg(buf, sizeof(buf), BOX_SEND_TIMEOUT);
}

//// Log entry
//void CUserBox::LogEntry(const char *msg,...)
//{
//	char buf[1024];
//	va_list argptr;
//
//	va_start(argptr, msg);
//	vsnprintf(buf, sizeof(buf), msg, argptr);
//	m_logger->Print(buf);
//	va_end(argptr);
//}

// Get profile info
const user_profile* CUserBox::GetProfileInfo()
{
	return (m_user_profile != NULL) ? m_user_profile->GetProfileInfo() : NULL;
} 